﻿using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using ZeroSum.DependencyInjection.Attributes;
using ZeroSum.Exceptions;
using ZeroSum.Plugin.Authentication.JwtBearer.Authentication.Internal;
using ZeroSum.Plugin.Authentication.JwtBearer.Configurations;

namespace ZeroSum.Plugin.Authentication.JwtBearer.Authentication;

/// <summary>
///     JwtBearer服务实现
/// </summary>
[Register(ServiceLifetime.Scoped)]
public sealed class JwtBearerService : IJwtBearerService
{
    private readonly IDistributedCache _distributedCache;
    private readonly JwtConfiguration _jwtOptions;

    /// <summary>
    ///     固定的 Claim 类型
    /// </summary>
    private readonly string[] _stationaryClaimTypes =
    {
        JwtRegisteredClaimNames.Iat,
        JwtRegisteredClaimNames.Nbf,
        JwtRegisteredClaimNames.Exp,
        JwtRegisteredClaimNames.Iss,
        JwtRegisteredClaimNames.Aud
    };

    public JwtBearerService(IConfiguration configuration, IDistributedCache distributedCache)
    {
        _distributedCache = distributedCache;
        _jwtOptions = configuration.Get<JwtStoreOptions>()?.Jwt ?? throw SystemConfigurationError.Of("系统配置没有正确配置Jwt！");
    }

    /// <summary>
    ///     生成 Token
    /// </summary>
    /// <param name="payload"></param>
    /// <param name="accessExpireTime">过期时间（分钟）</param>
    /// <returns></returns>
    public string? CreateToken(IDictionary<string, object>? payload, long? accessExpireTime = null)
    {
        var combinePayload = CombinePayload(payload, accessExpireTime);

        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.IssuerSigningKey!));
        var credentials = new SigningCredentials(securityKey, _jwtOptions.Algorithm);

        var tokenHandler = new JsonWebTokenHandler();
        return tokenHandler.CreateToken(JsonSerializer.Serialize(combinePayload), credentials);
    }

    /// <summary>
    ///     生成刷新 Token
    /// </summary>
    /// <param name="accessToken"></param>
    /// <returns></returns>
    public string? GenerateRefreshToken(string? accessToken)
    {
        // 分割Token
        var tokenParagraphs = accessToken?.Split('.', StringSplitOptions.RemoveEmptyEntries);

        var s = RandomNumberGenerator.GetInt32(10, tokenParagraphs![1].Length / 2 + 2);
        var l = RandomNumberGenerator.GetInt32(3, 13);

        var payload = new Dictionary<string, object>
        {
            {"f", tokenParagraphs[0]},
            {"e", tokenParagraphs[2]},
            {"s", s},
            {"l", l},
            {"k", tokenParagraphs[1].Substring(s, l)}
        };

        return CreateToken(payload, _jwtOptions.RefreshExpiredTime);
    }

    /// <summary>
    ///     自动刷新 Token 信息
    /// </summary>
    /// <param name="context"></param>
    /// <param name="httpContext"></param>
    /// <param name="tokenPrefix"></param>
    /// <returns></returns>
    public bool AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext? httpContext,
        string tokenPrefix = "Bearer ")
    {
        // 如果验证有效，则跳过刷新
        if (context.User.Identity is {IsAuthenticated: true}) return true;

        // 判断是否含有匿名特性
        if (httpContext!.GetEndpoint()?.Metadata?.GetMetadata<AllowAnonymousAttribute>() != null) return true;

        // 获取过期Token 和 刷新Token
        var expiredToken = GetJwtBearerToken(httpContext, tokenPrefix: tokenPrefix);
        var refreshToken = GetJwtBearerToken(httpContext, "X-Authorization", tokenPrefix);
        if (string.IsNullOrWhiteSpace(expiredToken) || string.IsNullOrWhiteSpace(refreshToken)) return false;

        // 交换新的 Token
        var accessToken = Exchange(expiredToken, refreshToken);
        if (string.IsNullOrWhiteSpace(accessToken)) return false;

        // 读取新的 Token Clamis
        var claims = ReadJwtToken(accessToken)?.Claims;
        if (claims == null) return false;

        // 创建身份信息
        var claimIdentity = new ClaimsIdentity("AuthenticationTypes.Federation");
        claimIdentity.AddClaims(claims);
        var claimsPrincipal = new ClaimsPrincipal(claimIdentity);

        // 设置 HttpContext.User 并登录
        httpContext!.User = claimsPrincipal;
        httpContext.SignInAsync(claimsPrincipal);

        const string accessTokenKey = "access-token";
        const string xAccessTokenKey = "x-access-token";
        const string accessControlExposeKey = "Access-Control-Expose-Headers";

        // 返回新的 Token
        httpContext.Response.Headers[accessTokenKey] = accessToken;
        // 返回新的 刷新Token
        httpContext.Response.Headers[xAccessTokenKey] = GenerateRefreshToken(accessToken);

        // 处理 axios 问题
        httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs);
        httpContext.Response.Headers[accessControlExposeKey] = string.Join(',',
            StringValues.Concat(acehs, new StringValues(new[] {accessTokenKey, xAccessTokenKey})).Distinct());

        return true;
    }

    /// <summary>
    ///     生成Token验证参数
    /// </summary>
    /// <returns></returns>
    public TokenValidationParameters CreateTokenValidationParameters()
    {
        return new TokenValidationParameters
        {
            // 验证签发方密钥
            ValidateIssuerSigningKey = _jwtOptions.ValidateIssuerSigningKey!.Value,
            // 签发方密钥
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.IssuerSigningKey!)),
            // 验证签发方
            ValidateIssuer = _jwtOptions.ValidateIssuer!.Value,
            // 设置签发方
            ValidIssuer = _jwtOptions.ValidIssuer,
            // 验证签收方
            ValidateAudience = _jwtOptions.ValidateAudience!.Value,
            // 设置接收方
            ValidAudience = _jwtOptions.ValidAudience,
            // 验证生存期
            ValidateLifetime = _jwtOptions.ValidateLifetime!.Value,
            // 过期时间容错值
            ClockSkew = TimeSpan.FromSeconds(_jwtOptions.ClockSkew!.Value)
        };
    }

    /// <summary>
    ///     通过过期Token 和 刷新Token 换取新的 Token
    /// </summary>
    /// <param name="expiredToken"></param>
    /// <param name="refreshToken"></param>
    /// <returns></returns>
    private string? Exchange(string? expiredToken, string? refreshToken)
    {
        // 交换刷新Token 必须原Token 已过期
        var (valid, _) = Validate(expiredToken);
        if (valid) return default;

        // 判断刷新Token 是否过期
        var (isValid, refreshTokenObj) = Validate(refreshToken);
        if (!isValid) return default;


        // 判断这个刷新Token 是否已刷新过
        var blacklistRefreshKey = "BLACKLIST_REFRESH_TOKEN:" + refreshToken;

        // 处理token并发容错问题
        var nowTime = DateTimeOffset.UtcNow;
        var cachedValue = _distributedCache?.GetString(blacklistRefreshKey);
        var isRefresh = !string.IsNullOrWhiteSpace(cachedValue); // 判断是否刷新过
        if (isRefresh)
            if (cachedValue != null)
            {
                var refreshTime = new DateTimeOffset(long.Parse(cachedValue), TimeSpan.Zero);
                // 处理并发时容差值
                if ((nowTime - refreshTime).TotalSeconds > _jwtOptions.ClockSkew) return default;
            }

        // 分割过期Token
        var tokenParagraphs = expiredToken?.Split('.', StringSplitOptions.RemoveEmptyEntries);
        if (tokenParagraphs is {Length: < 3}) return default;

        // 判断各个部分是否匹配
        if (refreshTokenObj != null &&
            !refreshTokenObj.GetPayloadValue<string>("f").Equals(tokenParagraphs?[0])) return default;
        if (refreshTokenObj != null &&
            !refreshTokenObj.GetPayloadValue<string>("e").Equals(tokenParagraphs?[2])) return default;
        if (refreshTokenObj != null && tokenParagraphs != null && !tokenParagraphs[1]
                .Substring(refreshTokenObj.GetPayloadValue<int>("s"), refreshTokenObj.GetPayloadValue<int>("l"))
                .Equals(refreshTokenObj.GetPayloadValue<string>("k"))) return default;

        // 获取过期 Token 的存储信息
        var oldToken = ReadJwtToken(expiredToken);
        var payload = oldToken?.Claims.Where(u => !_stationaryClaimTypes.Contains(u.Type))
            .ToDictionary(u => u.Type, u => (object) u.Value, new MultiClaimsDictionaryComparer());

        // 交换成功后登记刷新Token，标记失效
        if (isRefresh) return CreateToken(payload, _jwtOptions.AccessExpireTime);
        if (refreshTokenObj != null)
            _distributedCache?.SetString(blacklistRefreshKey, nowTime.Ticks.ToString(),
                new DistributedCacheEntryOptions
                {
                    AbsoluteExpiration = DateTimeOffset.FromUnixTimeSeconds(
                        refreshTokenObj.GetPayloadValue<long>(JwtRegisteredClaimNames.Exp))
                });

        return CreateToken(payload, _jwtOptions.AccessExpireTime);
    }

    /// <summary>
    ///     验证 Token
    /// </summary>
    /// <param name="accessToken"></param>
    /// <returns></returns>
    private (bool, JsonWebToken? jsonWebToken) Validate(string? accessToken)
    {
        // 加密Key
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.IssuerSigningKey!));
        var creds = new SigningCredentials(key, _jwtOptions.Algorithm);

        // 创建Token验证参数
        var tokenValidationParameters = CreateTokenValidationParameters();
        tokenValidationParameters.IssuerSigningKey ??= creds.Key;

        // 验证 Token
        var tokenHandler = new JsonWebTokenHandler();
        try
        {
            var tokenValidationResult = tokenHandler.ValidateToken(accessToken, tokenValidationParameters);
            if (!tokenValidationResult.IsValid) return (false, null)!;

            var jsonWebToken = tokenValidationResult.SecurityToken as JsonWebToken;
            return (true, jsonWebToken);
        }
        catch
        {
            return (false, default);
        }
    }

    /// <summary>
    ///     读取 Token，不含验证
    /// </summary>
    /// <param name="accessToken"></param>
    /// <returns></returns>
    private static JsonWebToken? ReadJwtToken(string? accessToken)
    {
        var tokenHandler = new JsonWebTokenHandler();
        return tokenHandler.CanReadToken(accessToken) ? tokenHandler.ReadJsonWebToken(accessToken) : default;
    }

    /// <summary>
    ///     获取 JWT Bearer Token
    /// </summary>
    /// <param name="httpContext"></param>
    /// <param name="headerKey"></param>
    /// <param name="tokenPrefix"></param>
    /// <returns></returns>
    private static string? GetJwtBearerToken(HttpContext? httpContext, string headerKey = "Authorization",
        string tokenPrefix = "Bearer ")
    {
        // 判断请求报文头中是否有 "Authorization" 报文头
        var bearerToken = httpContext!.Request.Headers[headerKey].ToString();
        if (string.IsNullOrWhiteSpace(bearerToken)) return default;

        var prefixLength = tokenPrefix.Length;
        return bearerToken.StartsWith(tokenPrefix, true, null)
               && bearerToken.Length > prefixLength
            ? bearerToken[prefixLength..]
            : default;
    }

    /// <summary>
    ///     组合 Claims 负荷
    /// </summary>
    /// <param name="payload"></param>
    /// <param name="expiredTime">过期时间，单位：分钟</param>
    /// <returns></returns>
    private IDictionary<string, object>? CombinePayload(IDictionary<string, object>? payload, long? expiredTime = null)
    {
        var datetimeOffset = DateTimeOffset.UtcNow;

        if (!payload!.ContainsKey(JwtRegisteredClaimNames.Iat))
            payload.Add(JwtRegisteredClaimNames.Iat, datetimeOffset.ToUnixTimeSeconds());

        if (!payload.ContainsKey(JwtRegisteredClaimNames.Nbf))
            payload.Add(JwtRegisteredClaimNames.Nbf, datetimeOffset.ToUnixTimeSeconds());

        if (!payload.ContainsKey(JwtRegisteredClaimNames.Exp))
        {
            var minute = expiredTime ?? _jwtOptions?.AccessExpireTime ?? 20;
            payload.Add(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(minute).ToUnixTimeSeconds());
        }

        if (!payload.ContainsKey(JwtRegisteredClaimNames.Iss))
            payload.Add(JwtRegisteredClaimNames.Iss, _jwtOptions?.ValidIssuer!);

        if (!payload.ContainsKey(JwtRegisteredClaimNames.Aud))
            payload.Add(JwtRegisteredClaimNames.Aud, _jwtOptions?.ValidAudience!);

        return payload;
    }
}